#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
RS485-DMX512接口模块
支持通过RS485总线控制实体DMX设备
"""

import serial
import serial.tools.list_ports
import threading
import time
import struct
from typing import List, Dict, Optional, Tuple, Callable
from enum import Enum
import logging

from dmx_core import DMXInterface, DMXUniverse


class RS485ProtocolType(Enum):
    """RS485协议类型"""
    STANDARD_DMX = "标准DMX512"
    MODBUS_RTU = "Modbus RTU"
    CUSTOM_PROTOCOL = "自定义协议"


class RS485DeviceType(Enum):
    """RS485设备类型"""
    DMX_CONVERTER = "DMX转换器"
    INTELLIGENT_LIGHT = "智能灯具"
    DIMMER_PACK = "调光包"
    MOTOR_CONTROLLER = "电机控制器"


class RS485Device:
    """RS485设备类"""
    
    def __init__(self, device_id: int, device_type: RS485DeviceType, 
                 name: str = "", start_address: int = 1, channel_count: int = 1):
        self.device_id = device_id
        self.device_type = device_type
        self.name = name or f"{device_type.value}_{device_id}"
        self.start_address = start_address
        self.channel_count = channel_count
        self.is_online = False
        self.last_response_time = 0
        self.firmware_version = ""
        self.status_data = {}
        
    def __str__(self):
        return f"{self.name} (ID:{self.device_id}, 地址:{self.start_address}-{self.start_address + self.channel_count - 1})"


class RS485DMXInterface(DMXInterface):
    """RS485-DMX接口类"""
    
    def __init__(self, port: str, baudrate: int = 115200, 
                 protocol: RS485ProtocolType = RS485ProtocolType.STANDARD_DMX,
                 name: str = "RS485-DMX"):
        super().__init__(name)
        self.port = port
        self.baudrate = baudrate
        self.protocol = protocol
        self.serial_conn = None
        self.devices = {}  # device_id -> RS485Device
        self.send_thread = None
        self.monitor_thread = None
        self.running = False
        
        # RS485控制信号
        self.rts_pin = True  # 用于控制发送/接收切换
        
        # 通信统计
        self.stats = {
            'packets_sent': 0,
            'packets_received': 0,
            'errors': 0,
            'last_error': None
        }
        
        # 设置日志
        self.logger = logging.getLogger(__name__)
        
    def connect(self) -> bool:
        """连接RS485接口"""
        try:
            self.serial_conn = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1,
                rtscts=False,  # 不使用硬件流控
                dsrdtr=False   # 不使用DTR/DSR
            )
            
            # 配置RS485控制
            try:
                if hasattr(serial, 'rs485') and hasattr(self.serial_conn, 'rs485_mode'):
                    # 如果支持RS485模式
                    rs485_settings = serial.rs485.RS485Settings()
                    rs485_settings.rts_level_for_tx = True
                    rs485_settings.rts_level_for_rx = False
                    rs485_settings.delay_before_tx = 0.001  # 1ms延迟
                    rs485_settings.delay_before_rx = 0.001
                    self.serial_conn.rs485_mode = rs485_settings
                    self.logger.info("已启用硬件RS485模式")
                else:
                    # 使用软件控制RTS引脚
                    self.logger.info("使用软件RS485控制")
            except Exception as e:
                self.logger.warning(f"RS485配置失败，使用软件控制: {e}")
                
            self.is_connected = True
            self.running = True
            
            # 启动监控线程
            self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
            self.monitor_thread.start()
            
            self.logger.info(f"RS485接口已连接: {self.port} @ {self.baudrate}bps")
            
            # 扫描设备
            self.scan_devices()
            
            return True
            
        except Exception as e:
            self.logger.error(f"RS485接口连接失败: {e}")
            self.is_connected = False
            return False
    
    def disconnect(self):
        """断开RS485接口"""
        self.running = False
        
        if self.monitor_thread:
            self.monitor_thread.join(timeout=1)
            
        if self.serial_conn and self.serial_conn.is_open:
            self.serial_conn.close()
            
        self.is_connected = False
        self.logger.info("RS485接口已断开")
    
    def send_dmx(self, universe: DMXUniverse) -> bool:
        """发送DMX数据到RS485设备"""
        if not self.is_connected or not self.serial_conn:
            return False
            
        try:
            if self.protocol == RS485ProtocolType.STANDARD_DMX:
                return self._send_standard_dmx(universe)
            elif self.protocol == RS485ProtocolType.MODBUS_RTU:
                return self._send_modbus_dmx(universe)
            elif self.protocol == RS485ProtocolType.CUSTOM_PROTOCOL:
                return self._send_custom_dmx(universe)
        except Exception as e:
            self.stats['errors'] += 1
            self.stats['last_error'] = str(e)
            self.logger.error(f"DMX数据发送失败: {e}")
            return False
            
        return True
    
    def _send_standard_dmx(self, universe: DMXUniverse) -> bool:
        """发送标准DMX512协议数据"""
        # 构建DMX512数据包
        packet = bytearray()
        
        # DMX512帧结构
        packet.append(0x7E)  # 帧头
        packet.append(0x06)  # 标签 (Output Only Send DMX Packet Request)
        packet.extend(struct.pack('<H', 513))  # 数据长度 (start code + 512 channels)
        packet.append(0x00)  # 序列号
        packet.append(0x00)  # 起始码
        packet.extend(universe.channels)  # 512通道数据
        packet.append(0xE7)  # 帧尾
        
        # 设置为发送模式
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = True
        
        # 发送数据
        bytes_sent = self.serial_conn.write(packet)
        self.serial_conn.flush()
        
        # 切换回接收模式
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = False
            
        self.stats['packets_sent'] += 1
        return bytes_sent == len(packet)
    
    def _send_modbus_dmx(self, universe: DMXUniverse) -> bool:
        """发送Modbus RTU格式的DMX数据"""
        success = True
        
        # 将512通道分组发送给不同设备
        for device in self.devices.values():
            if not device.is_online:
                continue
                
            # 提取设备对应的通道数据
            start_idx = device.start_address - 1
            end_idx = start_idx + device.channel_count
            channel_data = universe.channels[start_idx:end_idx]
            
            # 构建Modbus数据包
            packet = self._build_modbus_packet(
                device.device_id, 
                0x10,  # 写多个保持寄存器
                device.start_address,
                channel_data
            )
            
            try:
                # 发送并等待响应
                response = self._send_modbus_packet(packet)
                if not response:
                    success = False
                    device.is_online = False
                else:
                    device.last_response_time = time.time()
                    
            except Exception as e:
                self.logger.error(f"设备 {device.name} 通信失败: {e}")
                device.is_online = False
                success = False
                
        return success
    
    def _build_modbus_packet(self, device_id: int, function_code: int, 
                           start_address: int, data: bytes) -> bytes:
        """构建Modbus RTU数据包"""
        packet = bytearray()
        packet.append(device_id)  # 设备地址
        packet.append(function_code)  # 功能码
        packet.extend(struct.pack('>H', start_address))  # 起始地址
        packet.extend(struct.pack('>H', len(data)))  # 数据长度
        packet.append(len(data))  # 字节数
        packet.extend(data)  # 数据
        
        # 计算并添加CRC校验
        crc = self._calculate_crc16(packet)
        packet.extend(struct.pack('<H', crc))
        
        return bytes(packet)
    
    def _send_modbus_packet(self, packet: bytes) -> Optional[bytes]:
        """发送Modbus数据包并等待响应"""
        if not self.serial_conn:
            return None
            
        # 设置为发送模式
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = True
            
        # 发送数据
        self.serial_conn.write(packet)
        self.serial_conn.flush()
        
        # 切换到接收模式
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = False
        
        # 等待响应
        time.sleep(0.001)  # 1ms延迟
        
        response = bytearray()
        start_time = time.time()
        
        while time.time() - start_time < 0.1:  # 100ms超时
            if self.serial_conn.in_waiting > 0:
                data = self.serial_conn.read(self.serial_conn.in_waiting)
                response.extend(data)
                
                # 检查是否接收完整
                if len(response) >= 8:  # 最小响应长度
                    if self._verify_modbus_response(response):
                        self.stats['packets_received'] += 1
                        return bytes(response)
                        
        return None
    
    def _verify_modbus_response(self, response: bytes) -> bool:
        """验证Modbus响应"""
        if len(response) < 5:
            return False
            
        # 验证CRC
        data = response[:-2]
        received_crc = struct.unpack('<H', response[-2:])[0]
        calculated_crc = self._calculate_crc16(data)
        
        return received_crc == calculated_crc
    
    def _calculate_crc16(self, data: bytes) -> int:
        """计算CRC16校验码"""
        crc = 0xFFFF
        
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc = (crc >> 1) ^ 0xA001
                else:
                    crc >>= 1
                    
        return crc
    
    def _send_custom_dmx(self, universe: DMXUniverse) -> bool:
        """发送自定义协议DMX数据"""
        # 自定义协议实现
        packet = bytearray()
        
        # 自定义帧头
        packet.extend(b'\xAA\x55')  # 同步头
        packet.append(0x01)  # 协议版本
        packet.append(0x02)  # 命令：设置DMX数据
        packet.extend(struct.pack('<H', 512))  # 数据长度
        
        # 添加时间戳
        timestamp = int(time.time() * 1000) & 0xFFFFFFFF
        packet.extend(struct.pack('<I', timestamp))
        
        # DMX数据
        packet.extend(universe.channels)
        
        # 计算校验和
        checksum = sum(packet) & 0xFF
        packet.append(checksum)
        
        # 发送数据
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = True
            
        bytes_sent = self.serial_conn.write(packet)
        self.serial_conn.flush()
        
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = False
            
        self.stats['packets_sent'] += 1
        return bytes_sent == len(packet)
    
    def scan_devices(self) -> List[RS485Device]:
        """扫描RS485总线上的设备"""
        self.logger.info("开始扫描RS485设备...")
        found_devices = []
        
        if self.protocol == RS485ProtocolType.MODBUS_RTU:
            # Modbus设备扫描
            for device_id in range(1, 32):  # 扫描地址1-31
                if self._ping_modbus_device(device_id):
                    device = RS485Device(
                        device_id=device_id,
                        device_type=RS485DeviceType.INTELLIGENT_LIGHT,
                        name=f"Modbus设备_{device_id}",
                        start_address=device_id * 10,  # 假设每个设备占用10个通道
                        channel_count=10
                    )
                    device.is_online = True
                    device.last_response_time = time.time()
                    
                    self.devices[device_id] = device
                    found_devices.append(device)
                    
                    self.logger.info(f"发现设备: {device}")
                    
        elif self.protocol == RS485ProtocolType.CUSTOM_PROTOCOL:
            # 自定义协议设备扫描
            found_devices = self._scan_custom_devices()
            
        self.logger.info(f"扫描完成，发现 {len(found_devices)} 个设备")
        return found_devices
    
    def _ping_modbus_device(self, device_id: int) -> bool:
        """Ping Modbus设备"""
        try:
            # 发送读取设备信息命令
            packet = self._build_modbus_packet(device_id, 0x03, 0x0000, b'\x00\x01')
            response = self._send_modbus_packet(packet)
            return response is not None
        except:
            return False
    
    def _scan_custom_devices(self) -> List[RS485Device]:
        """扫描自定义协议设备"""
        # 实现自定义设备扫描逻辑
        found_devices = []
        
        # 发送设备发现广播
        discovery_packet = bytearray()
        discovery_packet.extend(b'\xAA\x55')  # 同步头
        discovery_packet.append(0x01)  # 协议版本
        discovery_packet.append(0xFF)  # 广播命令：设备发现
        discovery_packet.extend(b'\x00\x00')  # 无数据
        discovery_packet.append(sum(discovery_packet) & 0xFF)  # 校验和
        
        # 发送广播
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = True
            
        self.serial_conn.write(discovery_packet)
        self.serial_conn.flush()
        
        if hasattr(self.serial_conn, 'rts'):
            self.serial_conn.rts = False
        
        # 等待设备响应
        time.sleep(0.1)
        responses = self._collect_discovery_responses()
        
        for response in responses:
            device = self._parse_device_response(response)
            if device:
                self.devices[device.device_id] = device
                found_devices.append(device)
                
        return found_devices
    
    def _collect_discovery_responses(self) -> List[bytes]:
        """收集设备发现响应"""
        responses = []
        start_time = time.time()
        
        while time.time() - start_time < 1.0:  # 1秒收集时间
            if self.serial_conn.in_waiting > 0:
                data = self.serial_conn.read(self.serial_conn.in_waiting)
                # 解析响应包
                # 这里需要根据具体协议实现
                responses.append(data)
                
        return responses
    
    def _parse_device_response(self, response: bytes) -> Optional[RS485Device]:
        """解析设备响应"""
        # 根据具体协议实现设备信息解析
        if len(response) < 10:
            return None
            
        try:
            # 示例解析逻辑
            device_id = response[6]
            device_type_code = response[7]
            channel_count = response[8]
            
            device_type_map = {
                0x01: RS485DeviceType.DMX_CONVERTER,
                0x02: RS485DeviceType.INTELLIGENT_LIGHT,
                0x03: RS485DeviceType.DIMMER_PACK,
                0x04: RS485DeviceType.MOTOR_CONTROLLER
            }
            
            device_type = device_type_map.get(device_type_code, RS485DeviceType.INTELLIGENT_LIGHT)
            
            device = RS485Device(
                device_id=device_id,
                device_type=device_type,
                start_address=device_id * 10,
                channel_count=channel_count
            )
            device.is_online = True
            device.last_response_time = time.time()
            
            return device
            
        except Exception as e:
            self.logger.error(f"解析设备响应失败: {e}")
            return None
    
    def _monitor_loop(self):
        """设备监控循环"""
        while self.running:
            try:
                # 检查设备在线状态
                current_time = time.time()
                for device in self.devices.values():
                    if current_time - device.last_response_time > 5.0:  # 5秒超时
                        if device.is_online:
                            device.is_online = False
                            self.logger.warning(f"设备离线: {device.name}")
                
                # 定期读取设备状态
                self._read_device_status()
                
                time.sleep(1)  # 1秒检查一次
                
            except Exception as e:
                self.logger.error(f"监控循环错误: {e}")
                time.sleep(1)
    
    def _read_device_status(self):
        """读取设备状态"""
        for device in list(self.devices.values()):
            if not device.is_online:
                continue
                
            try:
                if self.protocol == RS485ProtocolType.MODBUS_RTU:
                    # 读取Modbus设备状态
                    status = self._read_modbus_device_status(device)
                    if status:
                        device.status_data = status
                        device.last_response_time = time.time()
                        
            except Exception as e:
                self.logger.error(f"读取设备 {device.name} 状态失败: {e}")
    
    def _read_modbus_device_status(self, device: RS485Device) -> Optional[Dict]:
        """读取Modbus设备状态"""
        try:
            # 读取设备信息寄存器
            packet = self._build_modbus_packet(device.device_id, 0x03, 0x1000, b'\x00\x10')
            response = self._send_modbus_packet(packet)
            
            if response and len(response) >= 8:
                # 解析状态数据
                data_length = response[2]
                status_data = response[3:3+data_length]
                
                return {
                    'temperature': status_data[0] if len(status_data) > 0 else 0,
                    'voltage': struct.unpack('>H', status_data[1:3])[0] if len(status_data) >= 3 else 0,
                    'current': struct.unpack('>H', status_data[3:5])[0] if len(status_data) >= 5 else 0,
                    'error_code': status_data[5] if len(status_data) > 5 else 0
                }
                
        except Exception as e:
            self.logger.error(f"读取设备状态失败: {e}")
            
        return None
    
    def get_device_list(self) -> List[RS485Device]:
        """获取设备列表"""
        return list(self.devices.values())
    
    def get_online_devices(self) -> List[RS485Device]:
        """获取在线设备列表"""
        return [device for device in self.devices.values() if device.is_online]
    
    def get_stats(self) -> Dict:
        """获取通信统计信息"""
        return self.stats.copy()
    
    def reset_stats(self):
        """重置统计信息"""
        self.stats = {
            'packets_sent': 0,
            'packets_received': 0,
            'errors': 0,
            'last_error': None
        }


def get_available_rs485_ports() -> List[Tuple[str, str]]:
    """获取可用的RS485端口"""
    ports = []
    
    for port in serial.tools.list_ports.comports():
        # 检查是否为RS485适配器
        description = port.description.lower()
        if any(keyword in description for keyword in ['rs485', 'rs-485', '485', 'dmx', 'usb-serial']):
            ports.append((port.device, f"{port.device} - {port.description}"))
        else:
            # 也包含普通串口，用户可能使用RS485转换器
            ports.append((port.device, f"{port.device} - {port.description}"))
            
    return ports


if __name__ == "__main__":
    # 测试RS485接口
    import logging
    logging.basicConfig(level=logging.INFO)
    
    print("RS485-DMX接口测试")
    
    # 获取可用端口
    ports = get_available_rs485_ports()
    print(f"可用端口: {ports}")
    
    if ports:
        # 创建RS485接口
        rs485_interface = RS485DMXInterface(
            port=ports[0][0],
            baudrate=115200,
            protocol=RS485ProtocolType.MODBUS_RTU
        )
        
        if rs485_interface.connect():
            print("RS485接口连接成功")
            
            # 扫描设备
            devices = rs485_interface.scan_devices()
            print(f"发现 {len(devices)} 个设备")
            
            # 测试发送数据
            from dmx_core import DMXUniverse
            universe = DMXUniverse()
            universe.set_channel(1, 255)
            universe.set_channel(2, 128)
            
            success = rs485_interface.send_dmx(universe)
            print(f"数据发送: {'成功' if success else '失败'}")
            
            time.sleep(2)
            rs485_interface.disconnect()
        else:
            print("RS485接口连接失败")
    else:
        print("未找到可用的RS485端口")
